"use strict";

exports.__esModule = true;
require("core-js/modules/es.error.cause.js");
var _base = require("../base");
var _ui2 = require("./ui");
var _focusDetector2 = require("../../utils/focusDetector");
var _object = require("../../helpers/object");
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
const PLUGIN_KEY = exports.PLUGIN_KEY = 'dialog';
const PLUGIN_PRIORITY = exports.PLUGIN_PRIORITY = 360;
const SHORTCUTS_GROUP = PLUGIN_KEY;
const SHORTCUTS_CONTEXT_NAME = `plugin:${PLUGIN_KEY}`;

/* eslint-disable jsdoc/require-description-complete-sentence */
/**
 * @plugin Dialog
 * @class Dialog
 *
 * @description
 * The dialog plugin provides a modal dialog system for Handsontable. It allows you to display custom content in modal dialogs
 * that overlay the table, providing a way to show notifications, error messages, loading indicators, or any other interactive content.
 *
 * In order to enable the dialog mechanism, {@link Options#dialog} option must be set to `true`.
 *
 * The plugin provides several configuration options to customize the dialog behavior and appearance:
 * - `content`: The string or HTMLElement content to display in the dialog (default: '')
 * - `customClassName`: Custom class name to apply to the dialog (default: '')
 * - `background`: Dialog background variant 'solid' | 'semi-transparent' (default: 'solid')
 * - `contentBackground`: Whether to show content background (default: false)
 * - `animation`: Whether to enable animations (default: true)
 * - `closable`: Whether the dialog can be closed (default: false)
 * - `a11y`: Object with accessibility options (default object below)
 * ```js
 * {
 *   role: 'dialog', // Role of the dialog 'dialog' | 'alertdialog' (default: 'dialog')
 *   ariaLabel: 'Dialog', // Label for the dialog (default: 'Dialog')
 *   ariaLabelledby: '', // ID of the element that labels the dialog (default: '')
 *   ariaDescribedby: '', // ID of the element that describes the dialog (default: ''),
 * }
 * ```
 *
 * @example
 *
 * ::: only-for javascript
 * ```js
 * // Enable dialog plugin with default options
 * dialog: true,
 *
 * // Enable dialog plugin with custom configuration
 * dialog: {
 *   content: 'Dialog content',
 *   customClassName: 'custom-dialog',
 *   background: 'semi-transparent',
 *   contentBackground: false,
 *   animation: false,
 *   closable: true,
 *   a11y: {
 *     role: 'dialog',
 *     ariaLabel: 'Dialog',
 *     ariaLabelledby: 'titleID',
 *     ariaDescribedby: 'descriptionID',
 *   }
 * }
 *
 * // Access to dialog plugin instance:
 * const dialogPlugin = hot.getPlugin('dialog');
 *
 * // Show a dialog programmatically:
 * dialogPlugin.show({
 *    content: '<h2>Custom Dialog</h2><p>This is a custom dialog content.</p>',
 *    closable: true,
 * });
 *
 * // Hide the dialog programmatically:
 * dialogPlugin.hide();
 *
 * // Check if dialog is visible:
 * const isVisible = dialogPlugin.isVisible();
 * ```
 * :::
 *
 * ::: only-for react
 * ```jsx
 * const MyComponent = () => {
 *   const hotRef = useRef(null);
 *
 *   useEffect(() => {
 *     const hot = hotRef.current.hotInstance;
 *     const dialogPlugin = hot.getPlugin('dialog');
 *
 *     dialogPlugin.show({
 *       content: <div>
 *         <h2>React Dialog</h2>
 *         <p>Dialog content rendered with React</p>
 *       </div>,
 *       closable: true
 *     });
 *   }, []);
 *
 *   return (
 *     <HotTable
 *       ref={hotRef}
 *       settings={{
 *         data: data,
 *         dialog: {
 *           customClassName: 'react-dialog',
 *           closable: true
 *         }
 *       }}
 *     />
 *   );
 * }
 * ```
 * :::
 *
 * ::: only-for angular
 * ```ts
 * hotSettings: Handsontable.GridSettings = {
 *   data: data,
 *   dialog: {
 *     customClassName: 'angular-dialog',
 *     closable: true
 *   }
 * }
 * ```
 *
 * ```html
 * <hot-table
 *   [settings]="hotSettings">
 * </hot-table>
 * ```
 * :::
 */
var _ui = /*#__PURE__*/new WeakMap();
var _isVisible = /*#__PURE__*/new WeakMap();
var _focusDetector = /*#__PURE__*/new WeakMap();
var _selectionState = /*#__PURE__*/new WeakMap();
var _Dialog_brand = /*#__PURE__*/new WeakSet();
class Dialog extends _base.BasePlugin {
  constructor() {
    super(...arguments);
    /**
     * Register shortcuts responsible for closing the dialog and navigating through the dialog.
     */
    _classPrivateMethodInitSpec(this, _Dialog_brand);
    /**
     * UI instance of the dialog plugin.
     *
     * @type {DialogUI}
     */
    _classPrivateFieldInitSpec(this, _ui, null);
    /**
     * Flag indicating if dialog is currently visible.
     *
     * @type {boolean}
     */
    _classPrivateFieldInitSpec(this, _isVisible, false);
    /**
     * Focus detector instance.
     *
     * @type {FocusDetector}
     */
    _classPrivateFieldInitSpec(this, _focusDetector, null);
    /**
     * Keeps the selection state that will be restored after the dialog is closed.
     *
     * @type {SelectionState | null}
     */
    _classPrivateFieldInitSpec(this, _selectionState, null);
  }
  static get PLUGIN_KEY() {
    return PLUGIN_KEY;
  }
  static get PLUGIN_PRIORITY() {
    return PLUGIN_PRIORITY;
  }
  static get DEFAULT_SETTINGS() {
    return {
      content: '',
      customClassName: '',
      background: 'solid',
      contentBackground: false,
      animation: true,
      closable: false,
      a11y: {
        role: 'dialog',
        ariaLabel: 'Dialog',
        ariaLabelledby: '',
        ariaDescribedby: ''
      }
    };
  }
  static get SETTINGS_VALIDATORS() {
    return {
      content: value => typeof value === 'string' || typeof HTMLElement !== 'undefined' && value instanceof HTMLElement || typeof DocumentFragment !== 'undefined' && value instanceof DocumentFragment,
      customClassName: value => typeof value === 'string',
      background: value => ['solid', 'semi-transparent'].includes(value),
      contentBackground: value => typeof value === 'boolean',
      animation: value => typeof value === 'boolean',
      closable: value => typeof value === 'boolean',
      a11y: value => (0, _object.isObject)(value) && (typeof (value === null || value === void 0 ? void 0 : value.role) === 'undefined' || ['dialog', 'alertdialog'].includes(value === null || value === void 0 ? void 0 : value.role)) && (typeof (value === null || value === void 0 ? void 0 : value.ariaLabel) === 'undefined' || typeof (value === null || value === void 0 ? void 0 : value.ariaLabel) === 'string') && (typeof (value === null || value === void 0 ? void 0 : value.ariaLabelledby) === 'undefined' || typeof (value === null || value === void 0 ? void 0 : value.ariaLabelledby) === 'string') && (typeof (value === null || value === void 0 ? void 0 : value.ariaDescribedby) === 'undefined' || typeof (value === null || value === void 0 ? void 0 : value.ariaDescribedby) === 'string')
    };
  }
  /**
   * Check if the plugin is enabled in the handsontable settings.
   *
   * @returns {boolean}
   */
  isEnabled() {
    return !!this.hot.getSettings()[PLUGIN_KEY];
  }

  /**
   * Enable plugin for this Handsontable instance.
   */
  enablePlugin() {
    if (this.enabled) {
      return;
    }
    if (!_classPrivateFieldGet(_ui, this)) {
      _classPrivateFieldSet(_ui, this, new _ui2.DialogUI({
        rootElement: this.hot.rootGridElement,
        isRtl: this.hot.isRtl()
      }));
      _classPrivateFieldGet(_ui, this).addLocalHook('clickDialogElement', () => _assertClassBrand(_Dialog_brand, this, _onDialogClick).call(this));
      _classPrivateFieldSet(_focusDetector, this, (0, _focusDetector2.installFocusDetector)(this.hot, _classPrivateFieldGet(_ui, this).getDialogElement(), {
        onFocus: from => {
          this.hot.getShortcutManager().setActiveContextName(SHORTCUTS_CONTEXT_NAME);
          this.hot.listen();
          this.hot.runHooks('afterDialogFocus', `tab_${from}`);
        }
      }));
    }
    _assertClassBrand(_Dialog_brand, this, _registerShortcuts).call(this);
    this.addHook('modifyFocusOnTabNavigation', from => _assertClassBrand(_Dialog_brand, this, _onFocusTabNavigation).call(this, from), 1);
    this.addHook('afterViewRender', () => _assertClassBrand(_Dialog_brand, this, _onAfterRender).call(this));
    this.addHook('afterListen', () => _assertClassBrand(_Dialog_brand, this, _onAfterListen).call(this));
    this.addHook('afterUnlisten', () => _assertClassBrand(_Dialog_brand, this, _onAfterUnlisten).call(this));
    super.enablePlugin();
  }

  /**
   * Update plugin state after Handsontable settings update.
   */
  updatePlugin() {
    this.disablePlugin();
    this.enablePlugin();
    super.updatePlugin();
  }

  /**
   * Disable plugin for this Handsontable instance.
   */
  disablePlugin() {
    this.hide();
    _assertClassBrand(_Dialog_brand, this, _unregisterShortcuts).call(this);
    super.disablePlugin();
  }
  /**
   * Check if the dialog is currently visible.
   *
   * @returns {boolean} True if the dialog is visible, false otherwise.
   */
  isVisible() {
    return _classPrivateFieldGet(_isVisible, this);
  }

  /**
   * Show dialog with given configuration.
   * Displays the dialog with the specified content and options.
   *
   * @param {object} options Dialog configuration object containing content and display options.
   * @param {string|HTMLElement|DocumentFragment} options.content The content to display in the dialog. Can be a string, HTMLElement, or DocumentFragment. Default: ''
   * @param {string} options.customClassName Custom CSS class name to apply to the dialog container. Default: ''
   * @param {'solid'|'semi-transparent'} options.background Dialog background variant. Default: 'solid'.
   * @param {boolean} options.contentBackground Whether to show content background. Default: false.
   * @param {boolean} options.animation Whether to enable animations when showing/hiding the dialog. Default: true.
   * @param {boolean} options.closable Whether the dialog can be closed by user interaction. Default: false.
   * @param {object} options.a11y Object with accessibility options.
   * @param {string} options.a11y.role The role of the dialog. Default: 'dialog'.
   * @param {string} options.a11y.ariaLabel The label of the dialog. Default: 'Dialog'.
   * @param {string} options.a11y.ariaLabelledby The ID of the element that labels the dialog. Default: ''.
   * @param {string} options.a11y.ariaDescribedby The ID of the element that describes the dialog. Default: ''.
   */
  show() {
    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    if (!this.enabled) {
      return;
    }
    if (this.isVisible()) {
      this.update(options);
      return;
    }
    this.hot.runHooks('beforeDialogShow');
    this.update(options);
    _classPrivateFieldGet(_ui, this).showDialog(this.getSetting('animation'));
    _classPrivateFieldSet(_isVisible, this, true);
    _classPrivateFieldSet(_selectionState, this, this.hot.selection.exportSelection());
    this.hot.deselectCell();
    this.hot.runHooks('afterDialogShow');
    const {
      activeElement
    } = this.hot.rootDocument;
    if (this.hot.rootWrapperElement.contains(activeElement) || this.hot.rootPortalElement.contains(activeElement)) {
      this.hot.unlisten();
      this.hot.getShortcutManager().setActiveContextName(SHORTCUTS_CONTEXT_NAME);
      this.hot.listen();
      _classPrivateFieldGet(_ui, this).focusDialog();
      this.hot.runHooks('afterDialogFocus', 'show');
    }
  }

  /**
   * Hide the currently open dialog.
   * Closes the dialog and restores the focus to the table.
   */
  hide() {
    if (!this.isVisible()) {
      return;
    }
    this.hot.runHooks('beforeDialogHide');
    _classPrivateFieldGet(_ui, this).hideDialog(this.getSetting('animation'));
    this.hot.getShortcutManager().setActiveContextName('grid');
    _classPrivateFieldSet(_isVisible, this, false);
    if (_classPrivateFieldGet(_selectionState, this)) {
      this.hot.selection.importSelection(_classPrivateFieldGet(_selectionState, this));
      this.hot.view.render();
      _classPrivateFieldSet(_selectionState, this, null);
    } else {
      this.hot.selectCell(0, 0);
    }
    this.hot.runHooks('afterDialogHide');
  }

  /**
   * Update the dialog configuration.
   *
   * @param {object} options Dialog configuration object containing content and display options.
   * @param {string|HTMLElement|DocumentFragment} options.content The content to display in the dialog. Can be a string, HTMLElement, or DocumentFragment. Default: ''
   * @param {string} options.customClassName Custom CSS class name to apply to the dialog container. Default: ''
   * @param {'solid'|'semi-transparent'} options.background Dialog background variant. Default: 'solid'.
   * @param {boolean} options.contentBackground Whether to show content background. Default: false.
   * @param {boolean} options.animation Whether to enable animations when showing/hiding the dialog. Default: true.
   * @param {boolean} options.closable Whether the dialog can be closed by user interaction. Default: false.
   * @param {object} options.a11y Object with accessibility options.
   * @param {string} options.a11y.role The role of the dialog. Default: 'dialog'.
   * @param {string} options.a11y.ariaLabel The label of the dialog. Default: 'Dialog'.
   * @param {string} options.a11y.ariaLabelledby The ID of the element that labels the dialog. Default: ''.
   * @param {string} options.a11y.ariaDescribedby The ID of the element that describes the dialog. Default: ''.
   */
  update(options) {
    if (!this.enabled) {
      return;
    }
    this.updatePluginSettings(options);
    _classPrivateFieldGet(_ui, this).updateDialog({
      isVisible: this.isVisible(),
      content: this.getSetting('content'),
      customClassName: this.getSetting('customClassName'),
      background: this.getSetting('background'),
      contentBackground: this.getSetting('contentBackground'),
      animation: this.getSetting('animation'),
      a11y: this.getSetting('a11y')
    });
  }

  /**
   * Focus the dialog.
   */
  focus() {
    _classPrivateFieldGet(_ui, this).focusDialog();
  }

  /**
   * Handle focus tab navigation event.
   *
   * @param {'from_above' | 'from_below'} from The direction from which the focus was modified.
   * @returns {boolean} Returns `false` to prevent the default focus behavior.
   */

  /**
   * Destroy dialog and reset plugin state.
   */
  destroy() {
    var _classPrivateFieldGet2;
    (_classPrivateFieldGet2 = _classPrivateFieldGet(_ui, this)) === null || _classPrivateFieldGet2 === void 0 || _classPrivateFieldGet2.destroyDialog();
    _classPrivateFieldSet(_ui, this, null);
    _classPrivateFieldSet(_isVisible, this, false);
    _classPrivateFieldSet(_focusDetector, this, null);
    _classPrivateFieldSet(_selectionState, this, null);
    super.destroy();
  }
}
exports.Dialog = Dialog;
function _registerShortcuts() {
  var _manager$getContext;
  const manager = this.hot.getShortcutManager();
  const pluginContext = (_manager$getContext = manager.getContext(SHORTCUTS_CONTEXT_NAME)) !== null && _manager$getContext !== void 0 ? _manager$getContext : manager.addContext(SHORTCUTS_CONTEXT_NAME);
  pluginContext.addShortcut({
    keys: [['Escape']],
    callback: () => {
      this.hide();
    },
    runOnlyIf: () => _classPrivateFieldGet(_isVisible, this) && this.getSetting('closable'),
    group: SHORTCUTS_GROUP
  });
  pluginContext.addShortcut({
    keys: [['Shift', 'Tab'], ['Tab']],
    preventDefault: false,
    callback: event => {
      this.hot._registerTimeout(() => {
        const {
          activeElement
        } = this.hot.rootDocument;
        if (!_classPrivateFieldGet(_ui, this).isInsideDialog(activeElement)) {
          this.hot.unlisten();
          return;
        }
        if (event.shiftKey) {
          this.hot.runHooks('dialogFocusPreviousElement');
        } else {
          this.hot.runHooks('dialogFocusNextElement');
        }
      }, 0);
    },
    group: SHORTCUTS_GROUP
  });
}
/**
 * Unregister shortcuts responsible for closing the dialog and navigating through the dialog.
 */
function _unregisterShortcuts() {
  const shortcutManager = this.hot.getShortcutManager();
  const pluginContext = shortcutManager.getContext(SHORTCUTS_CONTEXT_NAME);
  pluginContext.removeShortcutsByGroup(SHORTCUTS_GROUP);
}
function _onFocusTabNavigation(from) {
  if (this.isVisible()) {
    _classPrivateFieldGet(_focusDetector, this).focus(from);
    return false;
  }
}
/**
 * Handle dialog click event.
 */
function _onDialogClick() {
  if (this.isVisible() && !this.hot.isListening()) {
    this.hot.getShortcutManager().setActiveContextName(SHORTCUTS_CONTEXT_NAME);
    this.hot.runHooks('afterDialogFocus', 'click');
  }
  this.hot.listen();
}
/**
 * Called after the table is listened.
 */
function _onAfterListen() {
  _classPrivateFieldGet(_focusDetector, this).deactivate();
}
/**
 * Called after the table is unlistened.
 */
function _onAfterUnlisten() {
  _classPrivateFieldGet(_focusDetector, this).activate();
}
/**
 * Called after the rendering of the table is completed. It updates the width and
 * height of the dialog container to the same size as the table.
 */
function _onAfterRender() {
  const {
    view,
    rootWrapperElement,
    rootWindow
  } = this.hot;
  const width = view.isHorizontallyScrollableByWindow() ? view.getTotalTableWidth() : view.getWorkspaceWidth();
  _classPrivateFieldGet(_ui, this).updateWidth(width);
  const dialogInfo = rootWrapperElement.querySelector('.hot-display-license-info');
  if (dialogInfo) {
    const height = dialogInfo.offsetHeight;
    const marginTop = parseFloat(rootWindow.getComputedStyle(dialogInfo).marginTop);
    _classPrivateFieldGet(_ui, this).updateHeight(height + marginTop);
  }
}